package jmine.tec.sync;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import jmine.tec.rpc.common.entity.EntityRepresentation;
import jmine.tec.rpc.common.entity.EntityRest;
import jmine.tec.utils.collection.impl.SingletonMap;
import jmine.tec.utils.reflection.GenericTypeResolverImpl;
import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.dao.BeanNotFoundException;

/**
 * Subclasse de {@link AbstractEntityMaterializer} que soh precisa de uma representacao para materializar uma entidade.
 * 
 * @author takeshi
 * @param <A> o tipo da entidade local
 * @param <B> o tipo da representacao
 */
public abstract class AbstractSingleRepresentationEntityMaterializer<A extends Persistable, B extends EntityRepresentation> extends
        AbstractEntityMaterializer<A> {

    /**
     * Deve devolver o tipo da representacao. Por default, tenta resolver o valor do tipo generic B
     * 
     * @return {@link Class}
     */
    @SuppressWarnings("unchecked")
    protected Class<B> representationClass() {
        return (Class<B>) GenericTypeResolverImpl.getInstance().resolveTypeForFixedGenericType(
                AbstractSingleRepresentationEntityMaterializer.class.getTypeParameters()[1], this.getClass());
    }

    /**
     * Devolve a representacao
     * 
     * @param remotePrimaryKey o {@link Serializable}
     * @return B
     * @throws BeanNotFoundException e
     */
    protected B retrieveRepresentation(Serializable remotePrimaryKey) throws BeanNotFoundException {
        EntityRest<B> entityRest = this.getRestServiceProvider().provideByRepresentation(this.representationClass());
        if (entityRest != null) {
            return entityRest.findByIdentification(remotePrimaryKey);
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Map<Class<?>, Object> retrieveRepresentations(Serializable remoteId) throws BeanNotFoundException {
        return new SingletonMap<Class<?>, Object>(this.representationClass(), this.retrieveRepresentation(remoteId));
    }

    /**
     * Cria uma nova entidade. Pode ser sobrescrita por subclasses para prover um comportamento diferente do default
     * 
     * @param representation B
     * @param requestedId {@link Serializable}
     * @param map {@link Map}
     * @param cacheKey {@link Object}
     * @return A
     */
    protected A newEntity(B representation, Serializable requestedId, Map<Object, Object> map, Object cacheKey) {
        A bean = this.getLocalBaseDAO().createBean();
        map.put(cacheKey, bean);
        return bean;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected A newEntity(Map<Class<?>, Object> representations, Serializable requestedId, Map<Object, Object> localEntityCache,
            Object cacheKey) {
        return this.newEntity(this.representationClass().cast(representations.get(this.representationClass())), requestedId,
                localEntityCache, cacheKey);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doUpdateEntity(A entity, Map<Class<?>, Object> representations, Serializable requestedId) {
        this.doUpdateEntity(entity, this.getRepresentation(representations), requestedId);
    }

    /**
     * Metodo que as subclasses devem de fato sobrescrever
     * 
     * @param entity a entidade
     * @param representation a representacao
     * @param remotePrimaryKey {@link Serializable}
     */
    public abstract void doUpdateEntity(A entity, B representation, Serializable remotePrimaryKey);

    /**
     * {@inheritDoc}
     */
    @Override
    public Serializable naturalKeyFromRepresentations(Serializable remoteId, Map<Class<?>, Object> representations) {
        return this.naturalKeyFromRepresentation(this.getRepresentation(representations));
    }

    /**
     * Devolve a representacao dado o mapa de representacoes
     * 
     * @param representations {@link Map}
     * @return B
     */
    private B getRepresentation(Map<Class<?>, Object> representations) {
        return this.representationClass().cast(representations.get(this.representationClass()));
    }

    /**
     * @param representation the representation
     * @return {@link Serializable}
     */
    public abstract Serializable naturalKeyFromRepresentation(B representation);

    /**
     * @return {@link List}
     */
    public List<? extends Class<? extends EntityRepresentation>> remoteRepresentations() {
        List<Class<B>> list = Collections.singletonList(this.representationClass());
        return list;
    }

}
